using Sustainalytics.Entities;
using Sustainalytics.ESGRatings.BusinessLogic.Computing.Exceptions;
using Sustainalytics.ESGRatings.Computing.Entities;
using Sustainalytics.ESGRatings.Computing.Entities.Governance;
using Sustainalytics.ESGRatings.Primitives;
using Sustainalytics.ESGRatings.Primitives.Exceptions;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace Sustainalytics.ESGRatings.BusinessLogic.Computing
{
    internal class GovernanceSet : ComputationSet
    {
        private readonly object _syncObject = new object();

        /// <summary>
        /// Initializes a new instance of the <see cref="GovernanceSet"/> class. 
        /// The governance set.
        /// </summary>
        /// <param name="innerContext">
        /// The inner Context.
        /// </param>
        /// <param name="indicatorsConfiguration">
        /// The indicators Configuration.
        /// </param>
        /// <param name="companyEntities">
        /// The company Entities.
        /// </param>
        /// <exception cref="ComputeException">
        /// Exception when adding set
        /// </exception>
        /// <exception cref="Exception">
        /// A delegate callback throws an exception.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// <paramref>
        ///         <name>source</name>
        ///     </paramref>
        /// is null.
        /// </exception>
        public GovernanceSet(IndicatorsConfiguration indicatorsConfiguration, ICollection<GovernanceCompanyEntity> companyEntities)
            : base(companyEntities.Count, indicatorsConfiguration)
        {
            ComparativeGroups.Add((int)PerformanceType.Region, IndexByKey<Guid, string>.Create(companyEntities, x => x.Id, x => x.Region));

            ComparativeGroups.Add((int)PerformanceType.PeerGroup, IndexByKey<Guid, string>.Create(companyEntities, x => x.Id, x => x.PeerGroup));

            SetType = ComputationSetTypeEnum.GovernanceSet;

            try
            {
                SetCompaniesAttributes(companyEntities);
            }
            catch (ESGException exception)
            {
                throw new ComputeException("Exception when adding set", exception);
            }
            catch (ArgumentException argumentException)
            {
                throw new ComputeException("exception when adding set", argumentException);
            }
        }

        /// <summary>
        /// The add issue weights.
        /// </summary>
        /// <param name="matrixId">
        /// The matrix Id.
        /// </param>
        /// <param name="issuesWeights">
        /// The issues Weights.
        /// </param>
        /// <exception cref="UnknownMatrixException">
        /// When matrix id is not attached to set.
        /// </exception>
        /// <exception cref="UnknownCompanyException">
        /// When downcast to Governance entity fails.
        /// </exception>
        /// <exception cref="UnknownIndicatorException">
        /// Indicator templates does not provide any issue like the one mentioned in region issue weight matrix
        /// </exception>
        public void AddIssueWeights(Guid matrixId, ICollection<IssuesWeights> issuesWeights)
        {
            var weightData = new float[this.CompaniesNo * this.IndicatorsConfiguration.DisjunctClustersCount];

            WeightMatrixStruct matrix;
            if (!this.Matrices.TryGetValue(matrixId, out matrix))
            {
                throw new UnknownMatrixException(matrixId);
            }

            foreach (var regionIndicators in issuesWeights)
            {
                int[] companiesIndex = this.ComparativeGroup().GetIndex(regionIndicators.Region);

                foreach (var dataPair in regionIndicators.Weights)
                {
                    foreach (var companyIndex in companiesIndex)
                    {
                        int issueIndex = this.IndicatorsConfiguration.GetClusterIndex(dataPair.Code);

                        var index = this.GetColumnOrderIndex(companyIndex, issueIndex, this.CompaniesNo);
                        weightData[index] = dataPair.Value;
                    }
                }
            }
        }

        /// <exception cref="DuplicateEntityException">Adding an already added matrix.</exception>
        /// <exception cref="UnknownCompanyException">The matrix contains informationa about a company that was not registred or inssuficient data provided .</exception>
        /// <exception cref="UnknownIndicatorException">Matrix contains information about an indicator not present in the IndicatorTemplates set.</exception>
        /// <exception cref="ArgumentNullException">code is <see langword="null" />.</exception>
        public override void AddMatrix<T>(Guid matrixId, IEnumerable<EntityIndicators<T, float>> weights)
        {
            var entityIndicatorses = weights as EntityIndicators<T, float>[] ?? weights.ToArray();

            if (Matrices.ContainsKey(matrixId))
            {
                throw new DuplicateEntityException(matrixId, typeof(WeightMatrixStruct));
            }

            var internalId = NextId();

            var weightMatrixInfo = new WeightMatrixStruct
            {
                IsNaN = new BitArray(CompaniesNo * IndicatorsNo, true),
                MatrixId = matrixId,
                MatrixInternalId = internalId
            };

            var weightData = new float[CompaniesNo * IndicatorsNo];
            var profileHasEsgResearch = new byte[CompaniesNo];

            foreach (var regionIndicators in entityIndicatorses)
            {
                int[] companiesIndex = ComparativeGroup().GetIndex(regionIndicators.EntityId);
                foreach (var companyIndex in companiesIndex)
                {
                    profileHasEsgResearch[companyIndex] = 1;
                    foreach (var dataPair in regionIndicators.IndicatorsData)
                    {
                        int indicatorIndex = IndicatorsConfiguration.GetIndex(dataPair.Code);

                        var index = GetColumnOrderIndex(companyIndex, indicatorIndex, CompaniesNo);
                        weightData[index] = dataPair.Value;
                        weightMatrixInfo.IsNaN.Set(index, false);
                    }
                }
            }

            Matrices.Add(matrixId, weightMatrixInfo);
        }

        /// <exception cref="UnknownMatrixException">Matrix provided in options doesn't match the one attached to the set .</exception>
        public GovernanceComputeResult Ratings(Guid matrixId, bool includeWeightesScores, bool includeWeights)
        {
            WeightMatrixStruct matrixInfo;
            if (!Matrices.TryGetValue(matrixId, out matrixInfo))
            {
                throw new UnknownMatrixException(matrixId);
            }

            return new GovernanceComputeResult
            {
                CompaniesMap = ComparativeGroup().GetReverseIndexCopy(),
                Configuration = IndicatorsConfiguration,
                IncludeWeights = includeWeights,
                IncludeWeightedScores = includeWeightesScores,
                Ratings = null
            };
        }

        internal void AddCombinationModifiers(Guid matrixId, ICollection<CombinationModifier> modifiers)
        {
            var weightData = new float[IndicatorsNo * IndicatorsNo];

            WeightMatrixStruct matrix;
            if (!Matrices.TryGetValue(matrixId, out matrix))
                throw new UnknownMatrixException(matrixId);

            foreach (var dataPair in modifiers)
            {
                int indicatorX = IndicatorsConfiguration.GetIndex(dataPair.IndicatorCodeX);

                int indicatorY = IndicatorsConfiguration.GetIndex(dataPair.IndicatorCodeY);

                var index = GetColumnOrderIndex(indicatorX, indicatorY, IndicatorsNo);

                weightData[index] = dataPair.Weight;
            }

            //InnerContext.SetCombinationModifiers(Id, matrix.MatrixInternalId, weightData.Length, weightData);
        }

        private IIndex<Guid, string> ComparativeGroup(int comparativeGroupIndex = (int)PerformanceType.Region)
        {
            IIndex<Guid, string> comparativeGroup;
            if (!ComparativeGroups.TryGetValue(comparativeGroupIndex, out comparativeGroup))
            {
                throw new ESGEntityNotFound("Comparative groups is missing");
            }

            return comparativeGroup;
        }
        //TODO:this contains shallow conventions

        private void SetCompaniesAttributes(ICollection<GovernanceCompanyEntity> companyEntities)
        {
            Dictionary<int, CompanyAttributesStruct> comparativeGroupsArrays = new Dictionary<int, CompanyAttributesStruct>
            {
                {(int) PerformanceType.PeerGroup,new CompanyAttributesStruct {ComparativeGroupItemsCount=ComparativeGroup((int) PerformanceType.PeerGroup).SelectorsCount , Attributes=  new int[CompaniesNo]}},
                {(int) PerformanceType.Region,new CompanyAttributesStruct {ComparativeGroupItemsCount=ComparativeGroup().SelectorsCount , Attributes=  new int[CompaniesNo]}},
            };

            Dictionary<int, Func<GovernanceCompanyEntity, string>> comparativeGroupTypeExpression = new Dictionary<int, Func<GovernanceCompanyEntity, string>>
            {
                {(int) PerformanceType.PeerGroup, x=>x.PeerGroup},
                {(int) PerformanceType.Region, x=>x.Region}
            };

            foreach (var comparativeGroupType in Enum.GetValues(typeof(PerformanceType)).Cast<PerformanceType>())
            {
                var comparativeGroup = ComparativeGroup((int)comparativeGroupType);
                var expression = comparativeGroupTypeExpression[(int)comparativeGroupType];

                foreach (var company in companyEntities)
                {
                    int companyIndex = comparativeGroup.GetIndex(company.Id);
                    if (companyIndex < 0)
                    {
                        throw new UnknownCompanyException(company.Id);
                    }

                    if (expression != null)
                    {
                        int comparativeGroupIndex = comparativeGroup.GetGroupId(expression(company));
                        comparativeGroupsArrays[(int)comparativeGroupType].Attributes[companyIndex] = comparativeGroupIndex;
                    }
                }
            }

        }
    }
}